package com.want.register;

import com.want.annotation.EnableWantClient;
import com.want.annotation.WantClient;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

/**
 * @author WangZhiJian
 * @since 2021/12/6 0006
 */
public class WantClientRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private ResourceLoader resourceLoader;
    private Environment environment;

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registryWantRestClient(importingClassMetadata,registry);
    }

    /**
     * 注册使用{@link WantClient}的接口代理类到spring容器中
     * @param importingClassMetadata
     * @param registry
     */
    private void registryWantRestClient(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
        // 创建一个ClassPath的组件提供者,我们将定义什么是组件
        ClassPathScanningCandidateComponentProvider  scanner = getScanner();
        // 设置资源加载器
        scanner.setResourceLoader(resourceLoader);
        // 为自定义的组件提供者添加一个注解过滤,表明我们要包含 WantClient 注解的类文件
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(WantClient.class);
        scanner.addIncludeFilter(annotationTypeFilter);
        // 获取注入类的EnableWantClient的元数据
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableWantClient.class.getCanonicalName());
        // 扫描包为空则直接跳过
        if(annotationAttributes == null || annotationAttributes.get("scanBasePackage") == null)
            return;
        // 遍历所有的扫描包
        Stream.of((String[])annotationAttributes.get("scanBasePackage"))
                .filter(StringUtils::hasText)
                .distinct()
                .forEach(
                       path ->{
                           // 寻找满足我们定义的组件
                           Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(path);
                           // 遍历每一个组件
                           for (BeanDefinition candidateComponent : candidateComponents){
                               // 我们只关注带注解的组件
                               if (candidateComponent instanceof AnnotatedBeanDefinition) {
                                   AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                                   // 获取类的元数据和注解信息
                                   AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                                   Assert.isTrue(annotationMetadata.isInterface(),
                                           "@WantClient can only be specified on an interface");
                                   // 获取WantClient的元数据
                                   Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(WantClient.class.getCanonicalName());
                                    // 根据元数据注册WantClient的代理类
                                   registerWantClient(registry, annotationMetadata, attributes);
                               }
                           }
                       }
                );
    }

    /**
     * 组件扫描器
     * 重写判定
     * @return
     */
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                // 独立的类,不拥有父类
                if (beanDefinition.getMetadata().isIndependent()) {
                    // 不是一个注解
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    /**
     * 注册使用WantClient注解的接口的代理类到容器中
     * @param registry
     * @param annotationMetadata
     * @param attributes
     */
    private void registerWantClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        try{
            // 获取类型
            String className = annotationMetadata.getClassName();
            // 构建 BeanDefinitionBuilder
               BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(WantClientFactoryBean.class);
            // 为这个bean设置属性
            definition.addPropertyValue("type",Class.forName(className));
            // 获取bean名称
            String name = Optional.ofNullable(attributes).map(map -> (String)map.get("value")).filter(StringUtils::hasText).orElseGet(annotationMetadata::getClassName);
            // 带有名称和别名的BeanDefinition的持有者,可以注册为内部bean的占位符。
            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition.getBeanDefinition(), name);
            //注册
            BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        }catch (Throwable ex){
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}
